#include "rtsp2ws_server.h"
#include <fstream>
#include <atomic>
//***********************************************************
// PackH2642Fmp4
//***********************************************************
bool Rtsp2wsServer::PackH2642Fmp4::PushH264Data(PayloadType pt, uint32_t time_stamp,
                                                u_int32_t nalutype, std::vector<u_char>& one_nalu,
                                                std::vector<u_char>& vdata) {
    // std::cout << "b_head_:" << b_head_ << std::endl;
    if (nalutype == IDR) {
        int      len{0};
        uint8_t* frame = &one_nalu[4];
        len            = fmp4muxer_.generate_moof_mdat(frame, one_nalu.size() - 4);
        // printf("i-size:%d\n", len);
        std::vector<u_char> idata(frame, frame + len);
        if (b_head_ == true) {
            vdata.assign(idata.begin(), idata.end());
        }
        free(frame);
    } else if (nalutype == SLICE) {
        uint8_t* frame = &one_nalu[4];
        int      len;
        len = fmp4muxer_.generate_moof_mdat(frame, one_nalu.size() - 4);
        // printf("p-size:%d\n", len);
        std::vector<u_char> pdata(frame, frame + len);
        if (b_head_ == true) {
            vdata.assign(pdata.begin(), pdata.end());
        }
        free(frame);
    } else if (nalutype == H264_SPS) {
        // printf("one_nalu-sps-size:%d\n", one_nalu.size());
        vsps_.assign(one_nalu.begin() + 4, one_nalu.end());
        fi_.sps      = &vsps_[0];
        fi_.sps_size = vsps_.size();
    } else if (nalutype == H264_PPS) {
        // printf("one_nalu-pps-size:%d\n", one_nalu.size());
        vpps_.assign(one_nalu.begin() + 4, one_nalu.end());
        fi_.sps      = &vpps_[0];
        fi_.sps_size = vpps_.size();

        uint8_t* fmp4_header;
        int      len;
        fi_.sps      = &vsps_[0];
        fi_.sps_size = vsps_.size();
        fi_.pps      = &vpps_[0];
        fi_.pps_size = vpps_.size();
        // printf("vsps.size:%d vpps.size:%d\n", vsps.size(), vpps.size());
        len = fmp4muxer_.generate_ftyp_moov(fmp4_header, fi_);
        // printf("fmp4_header-size:%d\n", len);
        std::vector<u_char> hdata(fmp4_header, fmp4_header + len);
        if (b_head_ == false) {
            b_head_ = true;
            // fmp4func(FTYP_MOOV, hdata);
            vdata.assign(hdata.begin(), hdata.end());
        }

        free(fmp4_header);
    }
    return false;
}
//***********************************************************
// websock
//***********************************************************

void Rtsp2wsServer::Start(std::string addr, u_int32_t port) {
    if (!wsserver_.init("0.0.0.0", 2000)) {
        std::cout << "wsserver init failed: " << wsserver_.getLastError() << std::endl;
        return;
    }
    admincmd_help_ =
      "Server help:\n"
      "login password\n"
      "echo str\n"
      "stop\n";

    running_ = true;
    ws_thr_  = std::thread([this]() {
        while (running_.load(std::memory_order_relaxed)) {
            wsserver_.poll(this);
            std::this_thread::yield();
        }
     });

    // rtspcli_thr_ = std::thread([this]() { RtspCliServerFunc(); });

    std::cout << "Server running..." << std::endl;
    ws_thr_.join();
    std::cout << "Server stopped..." << std::endl;
}

void Rtsp2wsServer::Stop() {
    running_ = false;
}

void Rtsp2wsServer::RtspCliServerFunc(WSConn& conn, RtspClient& rtspclient, std::string uri) {
    while (running_) {
        // if ()
        // {
        // }
    }
}

// called when a new websocket connection is about to open
// optional: origin, protocol, extensions will be nullptr if not exist in the request headers
// optional: fill resp_protocol[resp_protocol_size] to add protocol to response headers
// optional: fill resp_extensions[resp_extensions_size] to add extensions to response headers
// return true if accept this new connection
bool Rtsp2wsServer::onWSConnect(WSConn& conn, const char* request_uri, const char* host,
                                const char* origin, const char* protocol, const char* extensions,
                                char* resp_protocol, uint32_t resp_protocol_size,
                                char* resp_extensions, uint32_t resp_extensions_size) {
    struct sockaddr_in addr;
    conn.getPeername(addr);
    std::cout << "ws connection from: " << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port)
              << std::endl;
    std::cout << "request_uri: " << request_uri << std::endl;
    conn.user_data.request_uri = request_uri;
    conn.user_data.id          = id_;
    conn.user_data.pack        = new PackH2642Fmp4();
    printf("onWSConnect-id:%d\n", id_);
    id_++;
    std::cout << "host: " << host << std::endl;
    if (origin) {
        std::cout << "origin: " << origin << std::endl;
    }
    if (protocol) {
        std::cout << "protocol: " << protocol << std::endl;
    }
    if (extensions) {
        std::cout << "extensions: " << extensions << std::endl;
    }

    std::unique_lock<std::mutex> lck(vwsconn_mtx_);
    vwsconn_.push_back(&conn);
    // std::vector<>

    return true;
}

// called when a websocket connection is closed
// status_code 1005 means no status code in the close msg
// status_code 1006 means not a clean close(tcp connection closed without a close msg)
void Rtsp2wsServer::onWSClose(WSConn& conn, uint16_t status_code, const char* reason) {
    std::cout << "ws close, status_code: " << status_code << ", reason: " << reason << std::endl;
    conn.user_data.rtsp_uri = "";
    delete conn.user_data.pack;
    std::unique_lock<std::mutex> lck(vwsconn_mtx_);
    for (size_t i = 0; i < vwsconn_.size(); i++) {
        if (vwsconn_[i]->user_data.id == conn.user_data.id) {
            vwsconn_.erase(vwsconn_.begin() + i);
            printf("onWSClose-id:%d\n", conn.user_data.id);
            break;
        }
    }
}

// onWSMsg is used if RecvSegment == false(by default), called when a whole msg is received
void Rtsp2wsServer::onWSMsg(WSConn& conn, uint8_t opcode, const uint8_t* payload, uint32_t pl_len) {
    // conn.
    if (opcode == websocket::OPCODE_PING) {
        conn.send(websocket::OPCODE_PONG, payload, pl_len);
        return;
    }
    if (opcode == websocket::OPCODE_BINARY) {
        printf("b-pl_len:%d\n", pl_len);
        std::string str((char*)payload, pl_len);
        printf("b-payload:%s\n", str.c_str());
        // std::string resp("sdfsadfafdsfaf");
        // conn.send(websocket::OPCODE_BINARY, (const uint8_t*)resp.data(), resp.size());
        // std::ifstream imgfs("1.png");
        // std::string   strpic = std::string(std::istreambuf_iterator<char>(imgfs),
        // std::istreambuf_iterator<char>()); printf("pic-size:%d\n", strpic.size());
        // std::vector<u_char> vdata;
        // vdata.resize(LWS_SEND_BUFFER_PRE_PADDING + strpic.size());
        // memcpy(&vdata[LWS_SEND_BUFFER_PRE_PADDING], strpic.c_str(), strpic.size());
        // conn.send(websocket::OPCODE_BINARY, (const uint8_t*)strpic.data(), strpic.size());
        return;
    }

    if (opcode != websocket::OPCODE_TEXT) {
        conn.close(1003, "not text msg");
        return;
    }
    printf("pl_len:%d\n", pl_len);
    std::string str((char*)payload, pl_len);
    printf("payload:%s\n", str.c_str());
    if (std::string("/ch0") == conn.user_data.request_uri) {
        printf("ch0:%s\n", str.c_str());
        if (std::string("GetStream") == str) {
            std::string resp("open codecs");
            conn.send(websocket::OPCODE_TEXT, (const uint8_t*)resp.data(), resp.size());
            // split
            // sendfmp4data_ = std::thread([&]() { SendFmp4DataFunc(conn); });
            // AddRtsp(conn, "rtsp://127.0.0.1:9554/re0");
            // AddRtsp(conn, "rtsp://127.0.0.1:8660/live0");
            AddRtsp(conn, "rtsp://admin:hy123456@10.5.1.78/");
        }
    } else if (std::string("/ch1") == conn.user_data.request_uri) {
        printf("ch1:%s\n", str.c_str());
        if (std::string("GetStream") == str) {
            std::string resp("open codecs");
            conn.send(websocket::OPCODE_TEXT, (const uint8_t*)resp.data(), resp.size());
            // split
            // sendfmp4data_ = std::thread([&]() { SendFmp4DataFunc(conn); });
            AddRtsp(conn, "rtsp://127.0.0.1:9554/re1");
            // AddRtsp(conn, "rtsp://admin:hy123456@10.5.1.78/");
        }
    } else if (std::string("/ch2") == conn.user_data.request_uri) {
        printf("ch2:%s\n", str.c_str());
        if (std::string("GetStream") == str) {
            std::string resp("open codecs");
            conn.send(websocket::OPCODE_TEXT, (const uint8_t*)resp.data(), resp.size());
            // split
            // sendfmp4data_ = std::thread([&]() { SendFmp4DataFunc(conn); });
            AddRtsp(conn, "rtsp://127.0.0.1:9554/re2");
            // AddRtsp(conn, "rtsp://admin:hy123456@10.5.1.78/");
        }
    }

    // conn.send(websocket::OPCODE_TEXT, (const uint8_t*)resp.data(), resp.size());
}

void Rtsp2wsServer::AddRtsp(WSConn& conn, std::string uri) {
    bool has_uri = false;
    {
        std::unique_lock<std::mutex>                 lck(addrsr_map_mtx_);
        std::map<std::string, std::thread>::iterator it;
        for (it = addrsr_map_.begin(); it != addrsr_map_.end(); ++it) {
            // std::cout << "key: " << it->first << " value: " << it->second << std::endl;
            if (it->first == uri) {
                if (conn_map_[uri] == true) {
                    has_uri = true;
                    printf("the same uri.\n");
                    break;
                } else {
                    if (addrsr_map_[uri].joinable()) {
                        addrsr_map_[uri].join();
                    }
                    addrsr_map_.erase(it->first);
                    printf("erase uri:%s\n", uri.c_str());
                }
            }
        }
    }
    conn.user_data.rtsp_uri = uri;
    if (has_uri == false) {
        std::unique_lock<std::mutex> lck(addrsr_map_mtx_);
        conn_map_[uri]   = true;
        addrsr_map_[uri] = std::thread([this, uri]() {
            SendFmp4DataFunc(uri);
            printf("exit SendFmp4DataFunc url:%s.\n", uri.c_str());
        });
    }
}

void Rtsp2wsServer::SendFmp4DataFunc(std::string uri) {
    std::atomic_int lost{10};
    RtspClient      m_rtspclient;
    m_rtspclient.SetStreamFunc(
      [&](PayloadType pt, uint32_t time_stamp, u_int32_t nalutype, std::vector<u_char>& one_nalu) {
          lost = 10;
          for (auto conn : vwsconn_) {
              if (conn->user_data.rtsp_uri == uri) {
                  std::vector<u_char> vdata;
                  conn->user_data.pack->PushH264Data(pt, time_stamp, nalutype, one_nalu, vdata);
                  if (vdata.size()) {
                      //   printf("vdata-size:%d\n", vdata.size());
                      conn->send(websocket::OPCODE_BINARY, &vdata[0], vdata.size());
                  }
              }
          }
      });
    std::cout << "PlayUri start ." << std::endl;
    m_rtspclient.PlayUri(uri);
    std::cout << "PlayUri end ." << std::endl;
    while (1) {
        if (lost) {
            lost--;
            if (lost < 3) {
                break;
            }
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    m_rtspclient.Stop();
    std::unique_lock<std::mutex> lck(addrsr_map_mtx_);
    conn_map_[uri] = false;
}
// onWSSegment is used if RecvSegment == true, called when a segment is received
// pl_start_idx: index in the whole msg for the 1st byte of payload
// fin: whether it's the last segment
void Rtsp2wsServer::onWSSegment(WSConn& conn, uint8_t opcode, const uint8_t* payload,
                                uint32_t pl_len, uint32_t pl_start_idx, bool fin) {
    std::cout << "error: onWSSegment should not be called" << std::endl;
}

//***********************************************************
// rtspclient
//***********************************************************